package com.fenxiangtech.mall.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fenxiangtech.mall.exception.*;
import com.fenxiangtech.mall.dao.OrderDao;
import com.fenxiangtech.mall.dto.OrderStatus;
import com.fenxiangtech.mall.dto.PageData;
import com.fenxiangtech.mall.dto.ProductStatus;
import com.fenxiangtech.mall.dto.Result;
import com.fenxiangtech.mall.entity.Address;
import com.fenxiangtech.mall.entity.Order;
import com.fenxiangtech.mall.entity.OrderProduct;
import com.fenxiangtech.mall.entity.Product;
import com.fenxiangtech.mall.enums.OrderStatusEnum;
import com.fenxiangtech.mall.service.AddressService;
import com.fenxiangtech.mall.service.OrderService;
import com.fenxiangtech.mall.service.ProductService;
import com.fenxiangtech.mall.service.UserTokenService;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author zhangjiashun
 */
@Service
public class OrderServiceImpl implements OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Autowired
    private ProductService productService;

    @Autowired
    private UserTokenService userTokenService;

    @Autowired
    private AddressService addressService;

    @Autowired
    private OrderDao orderDao;

    private ObjectMapper mapper = new ObjectMapper();

    @Override
    @Transactional
    public Result place(List<OrderProduct> orderProducts) {
        List<Product> products = this.getProductByOrder(orderProducts);
        OrderStatus orderStatus = this.getOrderStatus(orderProducts, products);
        Result result = new Result();
        result.setSuccess(orderStatus.isSuccess());
        if (result.isSuccess()) {
            Order order = this.createOrder(orderStatus);
            this.saveOrder(order, orderProducts);
            result.setData(order);
        } else {
            result.setData(orderStatus);
        }
        return result;
    }

    @Override
    public boolean orderBelongCurrentUser(Long orderID) {
        Long uid = userTokenService.getCurrentUID();
        int count = orderDao.countOrderByOrderAndUser(orderID, uid);
        return count > 0;
    }

    @Override
    public Order getOrderByID(Long orderID) {
        Order order = orderDao.getOrderByID(orderID);
        if (order == null) {
            throw new OrderMissException();
        }
        String snapItems = order.getSnapItems();
        String snapAddress = order.getSnapAddress();
        try {
            List<ProductStatus> items = mapper.readValue(snapItems, List.class);
            Address address = mapper.readValue(snapAddress, Address.class);
            order.setItems(items);
            order.setAddress(address);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return order;
    }

    @Override
    public int getOrderStatus(Long orderID) {
        return orderDao.getOrderStatus(orderID);
    }

    @Override
    public OrderStatus checkOrderStock(Long orderID) {
        List<OrderProduct> orderProducts = orderDao.listOrderProductByOrderID(orderID);
        List<Product> products = this.getProductByOrder(orderProducts);
        OrderStatus orderStatus = this.getOrderStatus(orderProducts, products);
        return orderStatus;
    }

    @Override
    public boolean recordPrepayID(String prepayID, Long orderID) {
        int effectedNum = orderDao.recordPrepayID(prepayID, orderID);
        return effectedNum > 0;
    }

    @Override
    public Order getOrderByOrderNo(String orderNo) {
        return orderDao.getOrderByOrderNo(orderNo);
    }

    @Override
    public boolean updateOrderStatus(Long orderID, OrderStatusEnum orderStatusEnum) {
        int effectedNum = orderDao.updateOrderStatus(orderID, orderStatusEnum.getStatus());
        return effectedNum > 0;
    }

    @Override
    public boolean reduceStock(OrderStatus orderStatus) {
        List<ProductStatus> productStatusList = orderStatus.getProductStatusList();
        for (ProductStatus productStatus : productStatusList) {
            Integer count = productStatus.getCount();
            Long id = productStatus.getId();
            boolean success = productService.reduceStock(id, count);
            if (!success) {
                return false;
            }
        }
        return true;
    }

    @Override
    public PageData<Order> getOrderByUser(Integer page, Integer size) {
        Long uid = userTokenService.getCurrentUID();
        Integer index = (page - 1) * size;
        List<Order> orders = orderDao.getOrderByUser(uid, index, size);
        Integer total = orderDao.getCountByUser(uid);
        return new PageData<Order>(page, size, total, orders);
    }

    @Override
    public PageData<Order> listOrder(Integer page, Integer size) {
        page = page < 1 ? 1 : page;
        size = size < 1 ? 1 : size;
        Integer index = (page -1) * size;
        List<Order> orders = orderDao.listOrder(index, size);
        Integer total = orderDao.countOrder();
        return new PageData<Order>(page, size, total, orders);
    }

    @Override
    public Result<Order> delivery(Long id) {
        Order order = orderDao.getOrderByID(id);
        if (order == null) {
            throw new OrderMissException();
        }
        if (order.getStatus() == OrderStatusEnum.UNPAID.getStatus()) {
            throw new UnpaidException();
        }
        if (order.getStatus() == OrderStatusEnum.DELIVERED.getStatus()) {
            throw new DeliveredException();
        }
        int effectedNum = orderDao.updateOrderStatus(id, OrderStatusEnum.DELIVERED.getStatus());
        if (effectedNum <= 0) {
            throw new UnknownErrorException();
        }
        order.setStatus(OrderStatusEnum.DELIVERED.getStatus());
        return new Result<>(true, order);
    }

    private boolean saveOrder(Order order, List<OrderProduct> orderProducts) {
        int effectedNum = orderDao.saveOrder(order);
        if (effectedNum <= 0) {
            throw new UnknownErrorException();
        }
        for (OrderProduct orderProduct : orderProducts) {
            orderProduct.setOrderId(order.getId());
        }
        effectedNum = orderDao.saveOrderProducts(orderProducts);
        if (effectedNum <= 0) {
            throw new UnknownErrorException();
        }
        return true;
    }

    private Order createOrder(OrderStatus status) {
        BigDecimal orderPrice = status.getOrderPrice();
        List<ProductStatus> productStatusList = status.getProductStatusList();
        ProductStatus productStatus = productStatusList.get(0);
        Long uid = userTokenService.getCurrentUID();
        Address address = addressService.getAddressByUserID(uid);
        Integer totalCount = 0;
        for (ProductStatus ps : productStatusList) {
            totalCount += ps.getCount();
        }
        String items = null;
        String addStr = null;
        try {
            items = mapper.writeValueAsString(productStatusList);
            addStr = mapper.writeValueAsString(address);
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
        }
        String orderNo = this.makeOrderNo();
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setUserId(uid);
        order.setTotalPrice(orderPrice);
        order.setStatus(OrderStatusEnum.UNPAID.getStatus());
        order.setSnapImg(productStatus.getMainImgUrl());
        if (productStatusList.size() > 1) {
            order.setSnapName(productStatus.getName() + "等");
        } else {
            order.setSnapName(productStatus.getName());
        }
        order.setTotalCount(totalCount);
        order.setSnapItems(items);
        order.setSnapAddress(addStr);
        order.setCreateTime(new Date().getTime());
        return order;
    }

    private String makeOrderNo() {
        String randomNumeric = RandomStringUtils.randomNumeric(12);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String dateStr = sdf.format(System.currentTimeMillis());
        return dateStr + randomNumeric;
    }

    private List<Product> getProductByOrder(List<OrderProduct> orderProducts) {
        List<Long> ids = new ArrayList<>();
        for (OrderProduct orderProduct : orderProducts) {
            Long productId = orderProduct.getProductId();
            ids.add(productId);
        }
        return productService.listProductByIDs(ids);
    }

    private OrderStatus getOrderStatus(List<OrderProduct> orderProducts, List<Product> products) {
        boolean success = true;
        BigDecimal orderPrice = new BigDecimal(0);
        List<ProductStatus> productStatusList = new ArrayList<>();
        for (OrderProduct orderProduct : orderProducts) {
            ProductStatus productStatus = this.getProductStatus(orderProduct.getProductId(),
                    orderProduct.getCount(), products);
            if (!productStatus.isHaveStock()) {
                success = false;
            }
            orderPrice = orderPrice.add(productStatus.getTotalPrice());
            productStatusList.add(productStatus);
        }
        return new OrderStatus(success, orderPrice, productStatusList);
    }

    private ProductStatus getProductStatus(Long oPID, Integer oCount, List<Product> products) {
        List<Long> ids = new ArrayList<>();
        Product product = null;
        for (Product p : products) {
            ids.add(p.getId());
            if (oPID == p.getId()) {
                product = p;
            }
        }
        if (!ids.contains(oPID)) {
            throw new ProductMissException("id为" + oPID + "的商品不存在, 订单创建失败");
        }
        BigDecimal price = product.getPrice();
        BigDecimal totalPrice = price.multiply(BigDecimal.valueOf(oCount));
        ProductStatus productStatus = new ProductStatus(product.getId(), product.getName(),
                product.getMainImgUrl(), price, oCount);
        productStatus.setTotalPrice(totalPrice);
        productStatus.setHaveStock(product.getStock() >= oCount);
        return productStatus;
    }
}
